package com.atguigu.gmall.list.service.impl;

import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.GoodsService;
import com.atguigu.gmall.model.list.*;

import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.HighlightQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    GoodsDao goodsDao;

    @Autowired
    ElasticsearchRestTemplate esRestTemplate;

    @Override
    public void onsale(Goods goods) {
        goodsDao.save(goods);
    }

    @Override
    public void cancleSale(Long skuId) {
        goodsDao.deleteById(skuId);
    }

    // 浏览器（拼装查询参数） ---- 检索参数请求发给 ---- gateway ----- web-all -----  list检索服务
    // ----- list服务收到请求 ----- 处理search的Controller调用service的search方法 ----
    // service.search 根据前端的param参数 ，构建出 Query（NativeSearchQuery）----
    // NativeSearchQuery --- 添加查询、分页、排序、高亮、聚合... --- esRestTemplate.search把这个dsl发给es进行检索
    // 检索完成后返回的数据，根据页面要的东西组装好返回给页面
    @Override
    public SearchResponseVo search(SearchParam param) {

        //检索的完整DSL 发给 ElasticSearch
        //根据前端传递的 SearchParam 所带的条件 动态 拼接出 ElasticSearch 用的检索的 DSL(Domain Specific Language)


        //去 goods 索引下，按照 query 的条件，查出所有 Goods类型的商品数据
        //1、构建检索条件 （根据SearchParam得到searchHits = {SearchHitsImpl@10843} "SearchHits{totalHits=4, totalHitsRelation=EQUAL_TO, maxScore=NaN, scrollId='null', searchHits={3 elements}, aggregations=org.elasticsearch.search.aggregations.Aggregations@e4febe23}"检索的DSL完整数据）
        Query query = buildQueryParam(param);

        //1、esRestTemplate 去 es 进行检索
        SearchHits<Goods> searchHits = null;
        try{
            searchHits = esRestTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));
            //打印出 esRestTemplate 给es发请求用的dsl
        }catch (Exception e){
            //1、页码超范围
            param.setPageNo(1);
            return search(param);
        }

        //2、分析从es返回的结果 SearchHits<Goods> ，封装成页面喜欢的 SearchResponseVo 即可
        SearchResponseVo responseVo = buildResponse(searchHits, param);


        return responseVo;
    }

    @Override
    public void updateGoodsHotScore(Long skuId, Long score) {
        Optional<Goods> dao = goodsDao.findById(skuId);
        if(dao.isPresent()){
            Goods goods = dao.get();
            goods.setHotScore(score);
            goodsDao.save(goods);
        }

    }


    /**
     * //TODO 根据 ES 检索到的所有结果 SearchHits<Goods> ，封装成页面能取值的 SearchResponseVo
     *
     * @param searchHits
     * @return
     */
    private SearchResponseVo buildResponse(SearchHits<Goods> searchHits, SearchParam param) {
        SearchResponseVo responseVo = new SearchResponseVo();
        //1、设置总记录数
        long totalHits = searchHits.getTotalHits();
        responseVo.setTotal(totalHits);

        //2、每页大小
        responseVo.setPageSize(param.getPageSize());

        //3、当前页
        responseVo.setPageNo(param.getPageNo());

        //4、总页码: 自己算？ 4/3 +1 = 2
        // 总记录数N / 每页大小 m
        long pages = totalHits % param.getPageSize() == 0 ? (totalHits / param.getPageSize()) : (totalHits / param.getPageSize() + 1);

        responseVo.setTotalPages(pages);

        //5、封装所有查到的商品数据
        //SearchHit<Goods> ： 代表命中的每一条记录
        List<SearchHit<Goods>> hits = searchHits.getSearchHits();
        //Function<? super T, ? extends R> mapper  //R apply(T t);
        List<Goods> goodsList = hits.stream()
                .map((item) -> { //传参就是hits这个流里面给我们流水线流入的元素
                    //元素加工方法
                    Goods goods = item.getContent();
                    //如果是模糊检索，需要用高亮的标题替换原标题
                    if(!StringUtils.isEmpty(param.getKeyword())){
                        //替换成高亮标题
                        String title = item.getHighlightField("title").get(0);
                        goods.setTitle(title);
                    }
                    return goods; //加工完成以后返回的元素
                }).collect(Collectors.toList());

        responseVo.setGoodsList(goodsList);

        //获取到聚合部分
        Aggregations aggregations = searchHits.getAggregations();

        //6、封装查到的所有商品涉及到的所有品牌信息
        List<SearchResponseTmVo> tmVosList = ((ParsedLongTerms) aggregations.get("tmId_agg"))
                .getBuckets().stream()
                .map((item) -> { //加工品牌id聚合的桶里的每一个数据
                    SearchResponseTmVo tmVo = new SearchResponseTmVo();
                    //品牌id
                    tmVo.setTmId(item.getKeyAsNumber().longValue());

                    //品牌名
                    String tmName = ((ParsedStringTerms) item.getAggregations().get("tmName_agg")).getBuckets().get(0).getKeyAsString();
                    tmVo.setTmName(tmName);

                    //品牌logo
                    String tmLogoUrl = ((ParsedStringTerms) item.getAggregations().get("tmLogoUrl_agg")).getBuckets().get(0).getKeyAsString();
                    tmVo.setTmLogoUrl(tmLogoUrl);

                    return tmVo;
                }).collect(Collectors.toList());

        //设置所有品牌列表
        responseVo.setTrademarkList(tmVosList);


        //7、封装查到的所有商品涉及到的所有平台属性信息

        ParsedLongTerms attrIdAgg =
                ((ParsedNested) aggregations.get("attr_agg"))
                        .getAggregations().get("attrId_agg");

        //商品涉及到的所有平台属性集合
        List<SearchResponseAttrVo> attrVos = attrIdAgg.getBuckets().stream()
                .map((item) -> {
                    SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
                    //属性id
                    attrVo.setAttrId(item.getKeyAsNumber().longValue());

                    //属性名
                    String attrName = ((ParsedStringTerms) item.getAggregations().get("attrName_agg")).getBuckets().get(0).getKeyAsString();
                    attrVo.setAttrName(attrName);

                    //属性值
                    List<String> attrsValueList = ((ParsedStringTerms) item.getAggregations()
                            .get("attrValue_agg"))
                            .getBuckets().stream()
                            .map((attrValue_agg_item) -> {
                                return attrValue_agg_item.getKeyAsString();
                            }).collect(Collectors.toList());

                    //List<String> attrValueList
                    attrVo.setAttrValueList(attrsValueList);
                    return attrVo;
                })
                .sorted((o1, o2) -> Integer.parseInt((o1.getAttrId() - o2.getAttrId())+"")) //int compare(T o1, T o2);升序排列
                .collect(Collectors.toList());

        responseVo.setAttrsList(attrVos);



        return responseVo;
    }


    /**
     * 根据前端传递来的参数 SearchParam, 构建出检索要用的 Query 对象；
     *
     * @param param
     * @return
     */
    private Query buildQueryParam(SearchParam param) {
        //1、使用 QueryBuilders 工具类，帮我们构建出 QueryBuilder
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //============查询开始============
        if (!StringUtils.isEmpty(param.getKeyword())) {
            //前端传递 keyword ，就需要全文检索商品标题
            boolQuery.must(QueryBuilders.matchQuery("title", param.getKeyword()));
        }

        if (param.getCategory3Id() != null) {
            //前端传递 category3Id， 就需要按照分类进行查询
            boolQuery.must(QueryBuilders.termQuery("category3Id", param.getCategory3Id()));
        }

        if (!StringUtils.isEmpty(param.getTrademark())) {
            //前端传递 trademark， 需要按照品牌id进行检索
            String[] split = param.getTrademark().split(":");
            long tmId = Long.parseLong(split[0]);
            boolQuery.must(QueryBuilders.termQuery("tmId", tmId));
        }

        if (param.getProps() != null && param.getProps().length > 0) {
            //前端传递 props=23:4G:运行内存&props=24:128G:机身存储
            //遍历每一组属性，都是一个nested查询
            for (String prop : param.getProps()) {
                //props=23:4G:运行内存
                String[] split = prop.split(":");

                BoolQueryBuilder boolQry = QueryBuilders.boolQuery();
                boolQry.must(QueryBuilders.termQuery("attrs.attrId", Long.parseLong(split[0])));
                boolQry.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));

                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolQry, ScoreMode.None);
                boolQuery.must(nestedQuery);
            }
        }

        //=========查询结束============

        //最终查询Query对象
        NativeSearchQuery query = new NativeSearchQuery(boolQuery);

        //=========分页开始========= 页面从0开始；
        query.setPageable(PageRequest.of(param.getPageNo() - 1, param.getPageSize()));
        //=========分页结束=========


        //==========排序开始=======
        if (!StringUtils.isEmpty(param.getOrder())) {
            Sort sort = null;
            // order=2:desc   order=1:asc
            String order = param.getOrder();
            String[] split = order.split(":");
            if (split[0].equals("1")) {
                //热度排序
                //by(Sort.Direction direction, String... properties)
                sort = Sort.by(split[1].equals("desc") ?
                                Sort.Direction.DESC : Sort.Direction.ASC,
                        "hotScore");
            }
            if (split[0].equals("2")) {
                //价格排序
                sort = Sort.by(split[1].equals("desc") ?
                                Sort.Direction.DESC : Sort.Direction.ASC,
                        "price");
            }
            //添加排序条件
            query.addSort(sort);
        }
        //==========排序结束==================


        //==========高亮条件===========
        if (!StringUtils.isEmpty(param.getKeyword())) {
            //需要高亮title
            HighlightBuilder title = new HighlightBuilder()
                    .field("title")
                    .preTags("<font color='red'>")
                    .postTags("</font>");
            //传入高亮条件
            HighlightQuery highlightQuery = new HighlightQuery(title);
            query.setHighlightQuery(highlightQuery);
        }
        //========高亮结束=======


        //=====================聚合开始=====================
        //TODO 构造聚合条件(任何情况ES都必须聚合分析所有商品涉及到的所有品牌和平台属性名值信息)
        //1、tmIdAgg大聚合
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmId_agg").field("tmId").size(1000);
        //1.2、tmIdAgg 子聚合
        //1.2.1 tmIdAgg 子聚合 1 tmName_agg
        TermsAggregationBuilder tmName_agg = AggregationBuilders.terms("tmName_agg").field("tmName").size(1);
        tmIdAgg.subAggregation(tmName_agg);


        //1.2.2 tmIdAgg 子聚合 2 tmLogoUrl_agg
        TermsAggregationBuilder tmLogoUrl_agg = AggregationBuilders.terms("tmLogoUrl_agg").field("tmLogoUrl").size(1);
        tmIdAgg.subAggregation(tmLogoUrl_agg);

        //1.end tmIdAgg大聚合 搞定
        query.addAggregation(tmIdAgg);


        //2、attr_agg 大聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");

        //2.1 attr_agg 大聚合 子聚合1  attrId_agg
        TermsAggregationBuilder attrId_agg = AggregationBuilders.terms("attrId_agg").field("attrs.attrId").size(1000);

        //2.1.1 attrId_agg 还有子聚合 1 attrName_agg
        TermsAggregationBuilder attrName_agg = AggregationBuilders.terms("attrName_agg").field("attrs.attrName").size(1);
        attrId_agg.subAggregation(attrName_agg);

        //2.1.2 attrId_agg 还有子聚合 2 attrValue_agg
        TermsAggregationBuilder attrValue_agg = AggregationBuilders.terms("attrValue_agg").field("attrs.attrValue").size(1000);
        attrId_agg.subAggregation(attrValue_agg);

        //2.1 end
        attr_agg.subAggregation(attrId_agg);
        query.addAggregation(attr_agg);

        return query;
    }
}
